package com.circuit.circuit;

// CirSim.java (c) 2010 by Paul Falstad
// For information about the theory behind this, see Electronic Circuit & System Simulation Methods by Pillage
import com.circuit.*;
import com.circuit.edit.EditDialog;
import com.circuit.edit.EditOptions;
import com.circuit.edit.Editable;
import com.circuit.elements.*;
import java.awt.*;
import java.awt.event.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilterInputStream;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

public class CirSim extends Frame implements ComponentListener, ActionListener, AdjustmentListener, MouseMotionListener, MouseListener, ItemListener, KeyListener {

    private Label titleLabel;
    private Button resetButton;
    private Button dumpMatrixButton;
    private MenuItem exportItem, exportLinkItem, importItem, exitItem, undoItem, redoItem,
            cutItem, copyItem, pasteItem, selectAllItem, optionsItem;
    private Menu optionsMenu;
    public Checkbox stoppedCheck;
    public CheckboxMenuItem voltsCheckItem;
    public CheckboxMenuItem powerCheckItem;
    public CheckboxMenuItem smallGridCheckItem;
    public CheckboxMenuItem showValuesCheckItem;
    public CheckboxMenuItem conductanceCheckItem;
    public CheckboxMenuItem euroResistorCheckItem;
    public CheckboxMenuItem printableCheckItem;
    public CheckboxMenuItem conventionCheckItem;
    private Scrollbar speedBar;
    private Scrollbar currentBar;
    private Label powerLabel;
    private Scrollbar powerBar;
    private PopupMenu elmMenu;
    private MenuItem elmEditMenuItem;
    private MenuItem elmDeleteMenuItem;
    public PopupMenu mainMenu;
    public Class addingClass;
    public int mouseMode = MODE_SELECT;
    public int tempMouseMode = MODE_SELECT;
    public String mouseModeStr = "Select";
    static final double pi = 3.14159265358979323846;
    public static final int MODE_ADD_ELM = 0;
    public static final int MODE_DRAG_ALL = 1;
    public static final int MODE_DRAG_ROW = 2;
    public static final int MODE_DRAG_COLUMN = 3;
    public static final int MODE_DRAG_SELECTED = 4;
    public static final int MODE_DRAG_POST = 5;
    public static final int MODE_SELECT = 6;
    public static final int infoWidth = 120;
    private int dragX, dragY, initDragX, initDragY;
    private int selectedSource;
    private Rectangle selectedArea;
    public int gridSize, gridMask, gridRound;
    private boolean dragging;
    public boolean analyzeFlag;
    private boolean dumpMatrix;
    public boolean useBufferedImage;
    private boolean isMac;
    private String ctrlMetaKey;
    public double t;
    private final int pause = 10;
    private int hintType = -1, hintItem1, hintItem2;
    private String stopMessage;
    public double timeStep;
    private static final int HINT_LC = 1;
    private static final int HINT_RC = 2;
    private static final int HINT_3DB_C = 3;
    private static final int HINT_TWINT = 4;
    private static final int HINT_3DB_L = 5;
    public List<CircuitElm> elmList;
//    Vector setupList;
    public CircuitElm dragElm, menuElm, mouseElm, stopElm;
    private boolean didSwitch = false;
    private int mousePost = -1;
    public CircuitElm plotXElm, plotYElm;
    private int draggingPost;
    private SwitchElm heldSwitchElm;
    private double circuitMatrix[][], circuitRightSide[],
            origRightSide[], origMatrix[][];
    public RowInfo circuitRowInfo[];
    public int circuitPermute[];
    public boolean circuitNonLinear;
    public int voltageSourceCount;
    public int circuitMatrixSize, circuitMatrixFullSize;
    public boolean circuitNeedsMap;
    public boolean useFrame;
    public static EditDialog editDialog;
    public Class dumpTypes[], shortcuts[];
    public static String muString = "u";
    public static String ohmString = "ohm";
    public String clipboard;
    public Rectangle circuitArea;
    public int circuitBottom;
    public List<String> undoStack, redoStack;
    private final Thread engine = null;
    public Dimension winSize;
    private Image dbimage;
    public static final int sourceRadius = 7;
    public static final double freqMult = 3.14159265 * 2 * 4;
    public boolean converged;
    public int subIterations;
    public List<CircuitNode> nodeList;
    public CircuitElm voltageSources[];
    private static final int resct = 6;
    private long lastTime = 0, lastFrameTime, lastIterTime, secTime = 0;
    private int frames = 0;
    private int steps = 0;
    private int framerate = 0, steprate = 0;
    public CircuitCanvas circuitCanvas;
    public Circuit applet;
    private String startCircuit = null;
    private String startLabel = null;
    private final String startCircuitText = null;
    private final String baseURL = "http://www.falstad.com/circuit/";
    private boolean shown = false;
    public static Container main;

    public CirSim(Circuit a) {
        super("Circuit Simulator v1.6h");
        applet = a;
        useFrame = false;
    }

    public void init() {
        String useFrameStr = null;
        boolean printable = false;
        boolean convention = true;

        CircuitElm.initClass(this);

        useFrame = (useFrameStr == null || !useFrameStr.equalsIgnoreCase("false"));
        if (useFrame) {
            main = this;
        } else {
            main = applet;
        }

        String os = System.getProperty("os.name");
        isMac = (os.indexOf("Mac ") == 0);
        ctrlMetaKey = (isMac) ? "\u2318" : "Ctrl";
        String jv = System.getProperty("java.class.version");
        double jvf = new Double(jv);
        if (jvf >= 48) {
            muString = "\u03bc";
            ohmString = "\u03a9";
            useBufferedImage = true;
        }

        dumpTypes = new Class[300];
        shortcuts = new Class[127];

        main.setLayout(new CircuitLayout());
        circuitCanvas = new CircuitCanvas(this);
        circuitCanvas.addComponentListener(this);
        circuitCanvas.addMouseMotionListener(this);
        circuitCanvas.addMouseListener(this);
        circuitCanvas.addKeyListener(this);
        main.add(circuitCanvas);

        mainMenu = new PopupMenu();
        MenuBar mb = new MenuBar();

        Menu m = new Menu("File");
        if (useFrame) {
            mb.add(m);
        } else {
            mainMenu.add(m);
        }
        m.add(importItem = getMenuItem("Import"));
        m.add(exportItem = getMenuItem("Export"));
        m.add(exportLinkItem = getMenuItem("Export Link"));
        m.addSeparator();
        m.add(exitItem = getMenuItem("Exit"));

        m = new Menu("Edit");
        m.add(undoItem = getMenuItem("Undo"));
        undoItem.setShortcut(new MenuShortcut(KeyEvent.VK_Z));
        m.add(redoItem = getMenuItem("Redo"));
        redoItem.setShortcut(new MenuShortcut(KeyEvent.VK_Z, true));
        m.addSeparator();
        m.add(cutItem = getMenuItem("Cut"));
        cutItem.setShortcut(new MenuShortcut(KeyEvent.VK_X));
        m.add(copyItem = getMenuItem("Copy"));
        copyItem.setShortcut(new MenuShortcut(KeyEvent.VK_C));
        m.add(pasteItem = getMenuItem("Paste"));
        pasteItem.setShortcut(new MenuShortcut(KeyEvent.VK_V));
        pasteItem.setEnabled(false);
        m.add(selectAllItem = getMenuItem("Select All"));
        selectAllItem.setShortcut(new MenuShortcut(KeyEvent.VK_A));
        if (useFrame) {
            mb.add(m);
        } else {
            mainMenu.add(m);
        }

        m = new Menu("Scope");
        if (useFrame) {
            mb.add(m);
        } else {
            mainMenu.add(m);
        }
        m.add(getMenuItem("Stack All", "stackAll"));
        m.add(getMenuItem("Unstack All", "unstackAll"));

        optionsMenu = m = new Menu("Options");
        if (useFrame) {
            mb.add(m);
        } else {
            mainMenu.add(m);
        }
        m.add(voltsCheckItem = getCheckItem("Show Voltage"));
        voltsCheckItem.setState(true);
        m.add(powerCheckItem = getCheckItem("Show Power"));
        m.add(showValuesCheckItem = getCheckItem("Show Values"));
        showValuesCheckItem.setState(true);
        //m.add(conductanceCheckItem = getCheckItem("Show Conductance"));
        m.add(smallGridCheckItem = getCheckItem("Small Grid"));
        m.add(euroResistorCheckItem = getCheckItem("European Resistors"));
        euroResistorCheckItem.setState(true);
        m.add(printableCheckItem = getCheckItem("White Background"));
        printableCheckItem.setState(printable);
        m.add(conventionCheckItem = getCheckItem("Conventional Current Motion"));
        conventionCheckItem.setState(convention);
        m.add(optionsItem = getMenuItem("Other Options..."));

        Menu circuitsMenu = new Menu("Circuits");
        if (useFrame) {
            mb.add(circuitsMenu);
        } else {
            mainMenu.add(circuitsMenu);
        }

        mainMenu.add(getClassCheckItem("Add Wire", WireElm.class.getSimpleName()));
        mainMenu.add(getClassCheckItem("Add Resistor", ResistorElm.class.getSimpleName()));

        Menu passMenu = new Menu("Passive Components");
        mainMenu.add(passMenu);
        passMenu.add(getClassCheckItem("Add Capacitor", CapacitorElm.class.getSimpleName()));
        passMenu.add(getClassCheckItem("Add Inductor", InductorElm.class.getSimpleName()));
        passMenu.add(getClassCheckItem("Add Switch", SwitchElm.class.getSimpleName()));
        passMenu.add(getClassCheckItem("Add Push Switch", PushSwitchElm.class.getSimpleName()));
        passMenu.add(getClassCheckItem("Add SPDT Switch", Switch2Elm.class.getSimpleName()));
        passMenu.add(getClassCheckItem("Add Potentiometer", PotElm.class.getSimpleName()));
        passMenu.add(getClassCheckItem("Add Transformer", TransformerElm.class.getSimpleName()));
        passMenu.add(getClassCheckItem("Add Relay", RelayElm.class.getSimpleName()));

        Menu inputMenu = new Menu("Inputs/Outputs");
        mainMenu.add(inputMenu);
        inputMenu.add(getClassCheckItem("Add Voltage Source (2-terminal)", DCVoltageElm.class.getSimpleName()));
        inputMenu.add(getClassCheckItem("Add LED", LEDElm.class.getSimpleName()));
        inputMenu.add(getClassCheckItem("Add Lamp (beta)", LampElm.class.getSimpleName()));
        inputMenu.add(getClassCheckItem("Add LED Matrix", LEDMatrixElm.class.getSimpleName()));
//	inputMenu.add(getClassCheckItem("Add Microphone Input", "SignalInElm.class.getSimpleName()));
//        inputMenu.add(getClassCheckItem("Add Speaker Output", "SignalOutElm.class.getSimpleName()));

        Menu activeMenu = new Menu("Active Components");
        mainMenu.add(activeMenu);
        activeMenu.add(getClassCheckItem("Add Diode", DiodeElm.class.getSimpleName()));
        activeMenu.add(getClassCheckItem("Add Transistor (bipolar, NPN)",
                NTransistorElm.class.getSimpleName()));
        activeMenu.add(getClassCheckItem("Add Transistor (bipolar, PNP)",
                PTransistorElm.class.getSimpleName()));
        activeMenu.add(getClassCheckItem("Add MOSFET (n-channel)",
                NMosfetElm.class.getSimpleName()));
        activeMenu.add(getClassCheckItem("Add MOSFET (p-channel)",
                PMosfetElm.class.getSimpleName()));
        activeMenu.add(getClassCheckItem("Add Triode", TriodeElm.class.getSimpleName()));

        Menu chipMenu = new Menu("Chips");
        mainMenu.add(chipMenu);
        chipMenu.add(getClassCheckItem("Add 7 Segment LED", SevenSegElm.class.getSimpleName()));

        Menu otherMenu = new Menu("Other");
        mainMenu.add(otherMenu);
        otherMenu.add(getClassCheckItem("Add Scope Probe", ProbeElm.class.getSimpleName()));
        otherMenu.add(getCheckItem("Drag All (Alt-drag)", "DragAll"));
        otherMenu.add(getCheckItem(
                isMac ? "Drag Row (Alt-S-drag, S-right)"
                        : "Drag Row (S-right)",
                "DragRow"));
        otherMenu.add(getCheckItem(
                isMac ? "Drag Column (Alt-\u2318-drag, \u2318-right)"
                        : "Drag Column (C-right)",
                "DragColumn"));
        otherMenu.add(getCheckItem("Drag Selected", "DragSelected"));
        otherMenu.add(getCheckItem("Drag Post (" + ctrlMetaKey + "-drag)",
                "DragPost"));

        mainMenu.add(getCheckItem("Select/Drag Selected (space or Shift-drag)", "Select"));
        main.add(mainMenu);

        main.add(resetButton = new Button("Reset"));
        resetButton.addActionListener(this);
        dumpMatrixButton = new Button("Dump Matrix");
        //main.add(dumpMatrixButton);
        dumpMatrixButton.addActionListener(this);
        stoppedCheck = new Checkbox("Stopped");
        stoppedCheck.addItemListener(this);
        main.add(stoppedCheck);

        main.add(new Label("Simulation Speed", Label.CENTER));

        // was max of 140
        main.add(speedBar = new Scrollbar(Scrollbar.HORIZONTAL, 3, 1, 0, 260));
        speedBar.addAdjustmentListener(this);

        main.add(new Label("Current Speed", Label.CENTER));
        currentBar = new Scrollbar(Scrollbar.HORIZONTAL,
                50, 1, 1, 100);
        currentBar.addAdjustmentListener(this);
        main.add(currentBar);

        main.add(powerLabel = new Label("Power Brightness", Label.CENTER));
        main.add(powerBar = new Scrollbar(Scrollbar.HORIZONTAL,
                50, 1, 1, 100));
        powerBar.addAdjustmentListener(this);
        powerBar.disable();
        powerLabel.disable();

        main.add(new Label("www.falstad.com"));

        if (useFrame) {
            main.add(new Label(""));
        }
        Font f = new Font("SansSerif", 0, 10);
        Label l;
        l = new Label("Current Circuit:");
        l.setFont(f);
        titleLabel = new Label("Label");
        titleLabel.setFont(f);
        if (useFrame) {
            main.add(l);
            main.add(titleLabel);
        }

        setGrid();
        elmList = new ArrayList<>();
//	setupList = new ArrayList();
        undoStack = new ArrayList<>();
        redoStack = new ArrayList<>();

        circuitCanvas.setBackground(Color.black);
        circuitCanvas.setForeground(Color.lightGray);

        elmMenu = new PopupMenu();
        elmMenu.add(elmEditMenuItem = getMenuItem("Edit"));
        elmMenu.add(elmDeleteMenuItem = getMenuItem("Delete"));
        main.add(elmMenu);

        getSetupList(circuitsMenu, false);
        if (useFrame) {
            setMenuBar(mb);
        }
        if (startCircuitText != null) {
            readSetup(startCircuitText);
        } else if (stopMessage == null && startCircuit != null) {
            readSetupFile(startCircuit, startLabel);
        } else {
            readSetup(null, 0, false);
        }

        if (useFrame) {
            Dimension screen = getToolkit().getScreenSize();
            resize(860, 640);
            handleResize();
            Dimension x = getSize();
            setLocation((screen.width - x.width) / 2,
                    (screen.height - x.height) / 2);
            show();
        } else {
            if (!powerCheckItem.getState()) {
                main.remove(powerBar);
                main.remove(powerLabel);
                main.validate();
            }
            hide();
            handleResize();
            applet.validate();
        }
        requestFocus();

        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent we) {
                destroyFrame();
            }
        }
        );
    }

    public void triggerShow() {
        if (!shown) {
            show();
        }
        shown = true;
    }

    @Override
    public void requestFocus() {
        super.requestFocus();
        circuitCanvas.requestFocus();
    }

    /*private PopupMenu buildScopeMenu(boolean t) {
        PopupMenu m = new PopupMenu();
        m.add(getMenuItem("Remove", "remove"));
        m.add(getMenuItem("Speed 2x", "speed2"));
        m.add(getMenuItem("Speed 1/2x", "speed1/2"));
        m.add(getMenuItem("Scale 2x", "scale"));
        m.add(getMenuItem("Max Scale", "maxscale"));
        m.add(getMenuItem("Stack", "stack"));
        m.add(getMenuItem("Unstack", "unstack"));
        m.add(getMenuItem("Reset", "reset"));
        main.add(m);
        return m;
    }*/

    private MenuItem getMenuItem(String s) {
        MenuItem mi = new MenuItem(s);
        mi.addActionListener(this);
        return mi;
    }

    private MenuItem getMenuItem(String s, String ac) {
        MenuItem mi = new MenuItem(s);
        mi.setActionCommand(ac);
        mi.addActionListener(this);
        return mi;
    }

    private CheckboxMenuItem getCheckItem(String s) {
        CheckboxMenuItem mi = new CheckboxMenuItem(s);
        mi.addItemListener(this);
        mi.setActionCommand("");
        return mi;
    }

    private CheckboxMenuItem getClassCheckItem(String s, String t) {
        try {
            Class c = Class.forName("com.circuit.elements." + t);
            CircuitElm elm = ElementUtils.constructElement(c, 0, 0);
            register(c, elm);
            if (elm.needsShortcut()) {
                s += " (" + (char) elm.getShortcut() + ")";
            }
            elm.delete();
        } catch (Exception ee) {
            ee.printStackTrace();
        }
        return getCheckItem(s, t);
    }

    private CheckboxMenuItem getCheckItem(String s, String t) {
        CheckboxMenuItem mi = new CheckboxMenuItem(s);
        mi.addItemListener(this);
        mi.setActionCommand(t);
        return mi;
    }

    private void register(Class c, CircuitElm elm) {
        int tt = elm.getDumpType();
        if (tt == 0) {
            System.out.println("no dump type: " + c);
            return;
        }

        int s = elm.getShortcut();
        if (elm.needsShortcut() && s == 0) {
            if (s == 0) {
                System.err.println("no shortcut " + c + " for " + c);
                return;
            } else if (s <= ' ' || s >= 127) {
                System.err.println("invalid shortcut " + c + " for " + c);
                return;
            }
        }

        Class dclass = elm.getDumpClass();
        if (dumpTypes[tt] != null && dumpTypes[tt] != dclass) {
            System.out.println("dump type conflict: " + c + " "
                    + dumpTypes[tt]);
            return;
        }

        dumpTypes[tt] = dclass;
        Class sclass = elm.getClass();
        if (elm.needsShortcut() && shortcuts[s] != null
                && shortcuts[s] != sclass) {
            System.err.println("shortcut conflict: " + c
                    + " (previously assigned to "
                    + shortcuts[s] + ")");
        } else {
            shortcuts[s] = sclass;
        }
    }

    private void handleResize() {
        winSize = circuitCanvas.getSize();
        if (winSize.width == 0) {
            return;
        }
        dbimage = main.createImage(winSize.width, winSize.height);
        int h = winSize.height / 5;
        /*if (h < 128 && winSize.height > 300)
         h = 128;*/
        circuitArea = new Rectangle(0, 0, winSize.width, winSize.height - h);
        int i;
        int minx = 1000, maxx = 0, miny = 1000, maxy = 0;
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            // centered text causes problems when trying to center the circuit,
            // so we special-case it here
            if (!ce.isCenteredText()) {
                minx = min(ce.x, min(ce.x2, minx));
                maxx = max(ce.x, max(ce.x2, maxx));
            }
            miny = min(ce.y, min(ce.y2, miny));
            maxy = max(ce.y, max(ce.y2, maxy));
        }
        // center circuit; we don't use snapGrid() because that rounds
        int dx = gridMask & ((circuitArea.width - (maxx - minx)) / 2 - minx);
        int dy = gridMask & ((circuitArea.height - (maxy - miny)) / 2 - miny);
        if (dx + minx < 0) {
            dx = gridMask & (-minx);
        }
        if (dy + miny < 0) {
            dy = gridMask & (-miny);
        }
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            ce.move(dx, dy);
        }
        // after moving elements, need this to avoid singular matrix probs
        needAnalyze();
        circuitBottom = 0;
    }

    private void destroyFrame() {
        if (applet == null) {
            dispose();
            System.exit(0);
        }
    }

    @Override
    public boolean handleEvent(Event ev) {
        if (ev.id == Event.WINDOW_DESTROY) {
            destroyFrame();
            return true;
        }
        return super.handleEvent(ev);
    }

    @Override
    public void paint(Graphics g) {
        circuitCanvas.repaint();
    }

    public void updateCircuit(Graphics realg) {
        CircuitElm realMouseElm;
        if (winSize == null || winSize.width == 0) {
            return;
        }
        if (analyzeFlag) {
            analyzeCircuit();
            analyzeFlag = false;
        }
        if (editDialog != null && editDialog.elm instanceof CircuitElm) {
            mouseElm = (CircuitElm) (editDialog.elm);
        }
        realMouseElm = mouseElm;
        if (mouseElm == null) {
            mouseElm = stopElm;
        }
        Graphics2D g = (Graphics2D) dbimage.getGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        CircuitElm.selectColor = Color.cyan;
        if (printableCheckItem.getState()) {
            CircuitElm.whiteColor = Color.black;
            CircuitElm.lightGrayColor = Color.black;
            g.setColor(Color.white);
        } else {
            CircuitElm.whiteColor = Color.white;
            CircuitElm.lightGrayColor = Color.lightGray;
            g.setColor(Color.black);
        }
        g.fillRect(0, 0, winSize.width, winSize.height);
        if (!stoppedCheck.getState()) {
            try {
                runCircuit();
            } catch (Exception e) {
                e.printStackTrace();
                analyzeFlag = true;
                circuitCanvas.repaint();
                return;
            }
        }
        if (!stoppedCheck.getState()) {
            long sysTime = System.currentTimeMillis();
            if (lastTime != 0) {
                int inc = (int) (sysTime - lastTime);
                double c = currentBar.getValue();
                c = java.lang.Math.exp(c / 3.5 - 14.2);
                CircuitElm.currentMult = 1.7 * inc * c;
                if (!conventionCheckItem.getState()) {
                    CircuitElm.currentMult = -CircuitElm.currentMult;
                }
            }
            if (sysTime - secTime >= 1000) {
                framerate = frames;
                steprate = steps;
                frames = 0;
                steps = 0;
                secTime = sysTime;
            }
            lastTime = sysTime;
        } else {
            lastTime = 0;
        }
        CircuitElm.powerMult = Math.exp(powerBar.getValue() / 4.762 - 7);

        int i;
        Font oldfont = g.getFont();
        for (i = 0; i != elmList.size(); i++) {
            if (powerCheckItem.getState()) {
                g.setColor(Color.gray);
            }
            /*else if (conductanceCheckItem.getState())
             g.setColor(Color.white);*/
            getElm(i).draw(g);
        }
        if (tempMouseMode == MODE_DRAG_ROW || tempMouseMode == MODE_DRAG_COLUMN
                || tempMouseMode == MODE_DRAG_POST || tempMouseMode == MODE_DRAG_SELECTED) {
            for (i = 0; i != elmList.size(); i++) {
                CircuitElm ce = getElm(i);
                ce.drawPost(g, ce.x, ce.y);
                ce.drawPost(g, ce.x2, ce.y2);
            }
        }
        int badnodes = 0;
        // find bad connections, nodes not connected to other elements which
        // intersect other elements' bounding boxes
        // debugged by hausen: nullPointerException
        if (nodeList != null) {
            for (i = 0; i != nodeList.size(); i++) {
                CircuitNode cn = getCircuitNode(i);
                if (!cn.internal && cn.links.size() == 1) {
                    int bb = 0, j;
                    CircuitNodeLink cnl = cn.links.get(0);
                    for (j = 0; j != elmList.size(); j++) { // TODO: (hausen) see if this change does not break stuff
                        CircuitElm ce = getElm(j);
                        if (cnl.elm != ce
                                && getElm(j).boundingBox.contains(cn.x, cn.y)) {
                            bb++;
                        }
                    }
                    if (bb > 0) {
                        g.setColor(Color.red);
                        g.fillOval(cn.x - 3, cn.y - 3, 7, 7);
                        badnodes++;
                    }
                }
            }
        }
        /*if (mouseElm != null) {
         g.setFont(oldfont);
         g.drawString("+", mouseElm.x+10, mouseElm.y);
         }*/
        if (dragElm != null
                && (dragElm.x != dragElm.x2 || dragElm.y != dragElm.y2)) {
            dragElm.draw(g);
        }
        g.setFont(oldfont);
        g.setColor(CircuitElm.whiteColor);
        if (stopMessage != null) {
            g.drawString(stopMessage, 10, circuitArea.height);
        } else {
            if (circuitBottom == 0) {
                calcCircuitBottom();
            }

            String info[] = new String[10];
            if (mouseElm != null) {
                if (mousePost == -1) {
                    mouseElm.getInfo(info);
                } else {
                    info[0] = "V = "
                            + CircuitElm.getUnitText(mouseElm.getPostVoltage(mousePost), "V");
                }

            } else {
                CircuitElm.showFormat.setMinimumFractionDigits(2);
                info[0] = "t = " + CircuitElm.getUnitText(t, "s");
                CircuitElm.showFormat.setMinimumFractionDigits(0);
            }
            if (hintType != -1) {
                for (i = 0; info[i] != null; i++);
                String s = getHint();
                if (s == null) {
                    hintType = -1;
                } else {
                    info[i] = s;
                }
            }
            int x = 0;
            x = max(x, winSize.width * 2 / 3);

            // count lines of data
            for (i = 0; info[i] != null; i++);
            if (badnodes > 0) {
                info[i++] = badnodes + ((badnodes == 1)
                        ? " bad connection" : " bad connections");
            }

            // find where to show data; below circuit, not too high unless we need it
            int ybase = winSize.height - 15 * i - 5;
            ybase = min(ybase, circuitArea.height);
            ybase = max(ybase, circuitBottom);
            for (i = 0; info[i] != null; i++) {
                g.drawString(info[i], x,
                        ybase + 15 * (i + 1));
            }
        }

        if (selectedArea != null) {
            g.setColor(CircuitElm.selectColor);
            g.drawRect(selectedArea.x, selectedArea.y, selectedArea.width, selectedArea.height);
        }

        mouseElm = realMouseElm;
        frames++;
        /*
         g.setColor(Color.white);
         g.drawString("Framerate: " + framerate, 10, 10);
         g.drawString("Steprate: " + steprate,  10, 30);
         g.drawString("Steprate/iter: " + (steprate/getIterCount()),  10, 50);
         g.drawString("iterc: " + (getIterCount()),  10, 70);
         */

        realg.drawImage(dbimage, 0, 0, this);
        if (!stoppedCheck.getState() && circuitMatrix != null) {
            // Limit to 50 fps (thanks to Jurgen Klotzer for this)
            long delay = 1000 / 50 - (System.currentTimeMillis() - lastFrameTime);
            //realg.drawString("delay: " + delay,  10, 90);
            if (delay > 0) {
                try {
                    Thread.sleep(delay);
                } catch (InterruptedException e) {
                }
            }

            circuitCanvas.repaint(0);
        }

        lastFrameTime = lastTime;
    }

    private String getHint() {
        CircuitElm c1 = getElm(hintItem1);
        CircuitElm c2 = getElm(hintItem2);
        if (c1 == null || c2 == null) {
            return null;
        }
        if (hintType == HINT_LC) {
            if (!(c1 instanceof InductorElm)) {
                return null;
            }
            if (!(c2 instanceof CapacitorElm)) {
                return null;
            }
            InductorElm ie = (InductorElm) c1;
            CapacitorElm ce = (CapacitorElm) c2;
            return "res.f = " + CircuitElm.getUnitText(1 / (2 * pi * Math.sqrt(ie.inductance
                    * ce.capacitance)), "Hz");
        }
        if (hintType == HINT_RC) {
            if (!(c1 instanceof ResistorElm)) {
                return null;
            }
            if (!(c2 instanceof CapacitorElm)) {
                return null;
            }
            ResistorElm re = (ResistorElm) c1;
            CapacitorElm ce = (CapacitorElm) c2;
            return "RC = " + CircuitElm.getUnitText(re.resistance * ce.capacitance,
                    "s");
        }
        if (hintType == HINT_3DB_C) {
            if (!(c1 instanceof ResistorElm)) {
                return null;
            }
            if (!(c2 instanceof CapacitorElm)) {
                return null;
            }
            ResistorElm re = (ResistorElm) c1;
            CapacitorElm ce = (CapacitorElm) c2;
            return "f.3db = "
                    + CircuitElm.getUnitText(1 / (2 * pi * re.resistance * ce.capacitance), "Hz");
        }
        if (hintType == HINT_3DB_L) {
            if (!(c1 instanceof ResistorElm)) {
                return null;
            }
            if (!(c2 instanceof InductorElm)) {
                return null;
            }
            ResistorElm re = (ResistorElm) c1;
            InductorElm ie = (InductorElm) c2;
            return "f.3db = "
                    + CircuitElm.getUnitText(re.resistance / (2 * pi * ie.inductance), "Hz");
        }
        if (hintType == HINT_TWINT) {
            if (!(c1 instanceof ResistorElm)) {
                return null;
            }
            if (!(c2 instanceof CapacitorElm)) {
                return null;
            }
            ResistorElm re = (ResistorElm) c1;
            CapacitorElm ce = (CapacitorElm) c2;
            return "fc = "
                    + CircuitElm.getUnitText(1 / (2 * pi * re.resistance * ce.capacitance), "Hz");
        }
        return null;
    }

    public void toggleSwitch(int n) {
        int i;
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            if (ce instanceof SwitchElm) {
                n--;
                if (n == 0) {
                    ((SwitchElm) ce).toggle();
                    analyzeFlag = true;
                    circuitCanvas.repaint();
                    return;
                }
            }
        }
    }

    public void needAnalyze() {
        analyzeFlag = true;
        circuitCanvas.repaint();
    }

    public CircuitNode getCircuitNode(int n) {
        if (n >= nodeList.size()) {
            return null;
        }
        return nodeList.get(n);
    }

    public CircuitElm getElm(int n) {
        if (n >= elmList.size()) {
            return null;
        }
        return elmList.get(n);
    }

    private void analyzeCircuit() {
        calcCircuitBottom();
        if (elmList.isEmpty()) {
            return;
        }
        stopMessage = null;
        stopElm = null;
        int i, j;
        int vscount = 0;
        nodeList = new ArrayList<>();
        boolean gotGround = false;
        CircuitElm volt = null;

        //System.out.println("ac1");
        // look for voltage or ground element
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            if (volt == null && ce instanceof VoltageElm) {
                volt = ce;
            }
        }

        // if no ground, and no rails, then the voltage elm's first terminal
        // is ground
        if (!gotGround && volt != null) {
            CircuitNode cn = new CircuitNode();
            Point pt = volt.getPost(0);
            cn.x = pt.x;
            cn.y = pt.y;
            nodeList.add(cn);
        } else {
            // otherwise allocate extra node for ground
            CircuitNode cn = new CircuitNode();
            cn.x = cn.y = -1;
            nodeList.add(cn);
        }
	//System.out.println("ac2");

        // allocate nodes and voltage sources
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            int inodes = ce.getInternalNodeCount();
            int ivs = ce.getVoltageSourceCount();
            int posts = ce.getPostCount();

            // allocate a node for each post and match posts to nodes
            for (j = 0; j != posts; j++) {
                Point pt = ce.getPost(j);
                int k;
                for (k = 0; k != nodeList.size(); k++) {
                    CircuitNode cn = getCircuitNode(k);
                    if (pt.x == cn.x && pt.y == cn.y) {
                        break;
                    }
                }
                if (k == nodeList.size()) {
                    CircuitNode cn = new CircuitNode();
                    cn.x = pt.x;
                    cn.y = pt.y;
                    CircuitNodeLink cnl = new CircuitNodeLink();
                    cnl.num = j;
                    cnl.elm = ce;
                    cn.links.add(cnl);
                    ce.setNode(j, nodeList.size());
                    nodeList.add(cn);
                } else {
                    CircuitNodeLink cnl = new CircuitNodeLink();
                    cnl.num = j;
                    cnl.elm = ce;
                    getCircuitNode(k).links.add(cnl);
                    ce.setNode(j, k);
                    // if it's the ground node, make sure the node voltage is 0,
                    // cause it may not get set later
                    if (k == 0) {
                        ce.setNodeVoltage(j, 0);
                    }
                }
            }
            for (j = 0; j != inodes; j++) {
                CircuitNode cn = new CircuitNode();
                cn.x = cn.y = -1;
                cn.internal = true;
                CircuitNodeLink cnl = new CircuitNodeLink();
                cnl.num = j + posts;
                cnl.elm = ce;
                cn.links.add(cnl);
                ce.setNode(cnl.num, nodeList.size());
                nodeList.add(cn);
            }
            vscount += ivs;
        }
        voltageSources = new CircuitElm[vscount];
        vscount = 0;
        circuitNonLinear = false;
	//System.out.println("ac3");

        // determine if circuit is nonlinear
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            if (ce.nonLinear()) {
                circuitNonLinear = true;
            }
            int ivs = ce.getVoltageSourceCount();
            for (j = 0; j != ivs; j++) {
                voltageSources[vscount] = ce;
                ce.setVoltageSource(j, vscount++);
            }
        }
        voltageSourceCount = vscount;

        int matrixSize = nodeList.size() - 1 + vscount;
        circuitMatrix = new double[matrixSize][matrixSize];
        circuitRightSide = new double[matrixSize];
        origMatrix = new double[matrixSize][matrixSize];
        origRightSide = new double[matrixSize];
        circuitMatrixSize = circuitMatrixFullSize = matrixSize;
        circuitRowInfo = new RowInfo[matrixSize];
        circuitPermute = new int[matrixSize];
        int vs = 0;
        for (i = 0; i != matrixSize; i++) {
            circuitRowInfo[i] = new RowInfo();
        }
        circuitNeedsMap = false;

        // stamp linear circuit elements
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            ce.stamp();
        }
	//System.out.println("ac4");

        // determine nodes that are unconnected
        boolean closure[] = new boolean[nodeList.size()];
        boolean tempclosure[] = new boolean[nodeList.size()];
        boolean changed = true;
        closure[0] = true;
        while (changed) {
            changed = false;
            for (i = 0; i != elmList.size(); i++) {
                CircuitElm ce = getElm(i);
                // loop through all ce's nodes to see if they are connected
                // to other nodes not in closure
                for (j = 0; j < ce.getPostCount(); j++) {
                    if (!closure[ce.getNode(j)]) {
                        if (ce.hasGroundConnection(j)) {
                            closure[ce.getNode(j)] = changed = true;
                        }
                        continue;
                    }
                    int k;
                    for (k = 0; k != ce.getPostCount(); k++) {
                        if (j == k) {
                            continue;
                        }
                        int kn = ce.getNode(k);
                        if (ce.getConnection(j, k) && !closure[kn]) {
                            closure[kn] = true;
                            changed = true;
                        }
                    }
                }
            }
            if (changed) {
                continue;
            }

            // connect unconnected nodes
            for (i = 0; i != nodeList.size(); i++) {
                if (!closure[i] && !getCircuitNode(i).internal) {
                    System.out.println("node " + i + " unconnected");
                    stampResistor(0, i, 1e8);
                    closure[i] = true;
                    changed = true;
                    break;
                }
            }
        }
        //System.out.println("ac5");

        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            // look for inductors with no current path
            if (ce instanceof InductorElm) {
                FindPathInfo fpi = new FindPathInfo(FindPathInfo.INDUCT, ce,
                        ce.getNode(1), this);
                // first try findPath with maximum depth of 5, to avoid slowdowns
                if (!fpi.findPath(ce.getNode(0), 5)
                        && !fpi.findPath(ce.getNode(0))) {
                    System.out.println(ce + " no path");
                    ce.reset();
                }
            }
            // look for voltage source loops
            if ((ce instanceof VoltageElm && ce.getPostCount() == 2)
                    || ce instanceof WireElm) {
                FindPathInfo fpi = new FindPathInfo(FindPathInfo.VOLTAGE, ce,
                        ce.getNode(1), this);
                if (fpi.findPath(ce.getNode(0))) {
                    stop("Voltage source/wire loop with no resistance!", ce);
                    return;
                }
            }
            // look for shorted caps, or caps w/ voltage but no R
            if (ce instanceof CapacitorElm) {
                FindPathInfo fpi = new FindPathInfo(FindPathInfo.SHORT, ce,
                        ce.getNode(1), this);
                if (fpi.findPath(ce.getNode(0))) {
                    System.out.println(ce + " shorted");
                    ce.reset();
                } else {
                    fpi = new FindPathInfo(FindPathInfo.CAP_V, ce, ce.getNode(1), this);
                    if (fpi.findPath(ce.getNode(0))) {
                        stop("Capacitor loop with no resistance!", ce);
                        return;
                    }
                }
            }
        }
	//System.out.println("ac6");

        // simplify the matrix; this speeds things up quite a bit
        for (i = 0; i != matrixSize; i++) {
            int qm = -1, qp = -1;
            double qv = 0;
            RowInfo re = circuitRowInfo[i];
            /*System.out.println("row " + i + " " + re.lsChanges + " " + re.rsChanges + " " +
             re.dropRow);*/
            if (re.lsChanges || re.dropRow || re.rsChanges) {
                continue;
            }
            double rsadd = 0;

            // look for rows that can be removed
            for (j = 0; j != matrixSize; j++) {
                double q = circuitMatrix[i][j];
                if (circuitRowInfo[j].type == RowInfo.ROW_CONST) {
                    // keep a running total of const values that have been
                    // removed already
                    rsadd -= circuitRowInfo[j].value * q;
                    continue;
                }
                if (q == 0) {
                    continue;
                }
                if (qp == -1) {
                    qp = j;
                    qv = q;
                    continue;
                }
                if (qm == -1 && q == -qv) {
                    qm = j;
                    continue;
                }
                break;
            }
            //System.out.println("line " + i + " " + qp + " " + qm + " " + j);
	    /*if (qp != -1 && circuitRowInfo[qp].lsChanges) {
             System.out.println("lschanges");
             continue;
             }
             if (qm != -1 && circuitRowInfo[qm].lsChanges) {
             System.out.println("lschanges");
             continue;
             }*/
            if (j == matrixSize) {
                if (qp == -1) {
                    stop("Matrix error", null);
                    return;
                }
                RowInfo elt = circuitRowInfo[qp];
                if (qm == -1) {
                    // we found a row with only one nonzero entry; that value
                    // is a constant
                    int k;
                    for (k = 0; elt.type == RowInfo.ROW_EQUAL && k < 100; k++) {
                        // follow the chain
			/*System.out.println("following equal chain from " +
                         i + " " + qp + " to " + elt.nodeEq);*/
                        qp = elt.nodeEq;
                        elt = circuitRowInfo[qp];
                    }
                    if (elt.type == RowInfo.ROW_EQUAL) {
                        // break equal chains
                        //System.out.println("Break equal chain");
                        elt.type = RowInfo.ROW_NORMAL;
                        continue;
                    }
                    if (elt.type != RowInfo.ROW_NORMAL) {
                        System.out.println("type already " + elt.type + " for " + qp + "!");
                        continue;
                    }
                    elt.type = RowInfo.ROW_CONST;
                    elt.value = (circuitRightSide[i] + rsadd) / qv;
                    circuitRowInfo[i].dropRow = true;
                    //System.out.println(qp + " * " + qv + " = const " + elt.value);
                    i = -1; // start over from scratch
                } else if (circuitRightSide[i] + rsadd == 0) {
                    // we found a row with only two nonzero entries, and one
                    // is the negative of the other; the values are equal
                    if (elt.type != RowInfo.ROW_NORMAL) {
                        //System.out.println("swapping");
                        int qq = qm;
                        qm = qp;
                        qp = qq;
                        elt = circuitRowInfo[qp];
                        if (elt.type != RowInfo.ROW_NORMAL) {
                            // we should follow the chain here, but this
                            // hardly ever happens so it's not worth worrying
                            // about
                            System.out.println("swap failed");
                            continue;
                        }
                    }
                    elt.type = RowInfo.ROW_EQUAL;
                    elt.nodeEq = qm;
                    circuitRowInfo[i].dropRow = true;
                    //System.out.println(qp + " = " + qm);
                }
            }
        }
	//System.out.println("ac7");

        // find size of new matrix
        int nn = 0;
        for (i = 0; i != matrixSize; i++) {
            RowInfo elt = circuitRowInfo[i];
            if (elt.type == RowInfo.ROW_NORMAL) {
                elt.mapCol = nn++;
                //System.out.println("col " + i + " maps to " + elt.mapCol);
                continue;
            }
            if (elt.type == RowInfo.ROW_EQUAL) {
                RowInfo e2;
                // resolve chains of equality; 100 max steps to avoid loops
                for (j = 0; j != 100; j++) {
                    e2 = circuitRowInfo[elt.nodeEq];
                    if (e2.type != RowInfo.ROW_EQUAL) {
                        break;
                    }
                    if (i == e2.nodeEq) {
                        break;
                    }
                    elt.nodeEq = e2.nodeEq;
                }
            }
            if (elt.type == RowInfo.ROW_CONST) {
                elt.mapCol = -1;
            }
        }
        for (i = 0; i != matrixSize; i++) {
            RowInfo elt = circuitRowInfo[i];
            if (elt.type == RowInfo.ROW_EQUAL) {
                RowInfo e2 = circuitRowInfo[elt.nodeEq];
                if (e2.type == RowInfo.ROW_CONST) {
                    // if something is equal to a const, it's a const
                    elt.type = e2.type;
                    elt.value = e2.value;
                    elt.mapCol = -1;
                    //System.out.println(i + " = [late]const " + elt.value);
                } else {
                    elt.mapCol = e2.mapCol;
                    //System.out.println(i + " maps to: " + e2.mapCol);
                }
            }
        }
	//System.out.println("ac8");

        /*System.out.println("matrixSize = " + matrixSize);
	
         for (j = 0; j != circuitMatrixSize; j++) {
         System.out.println(j + ": ");
         for (i = 0; i != circuitMatrixSize; i++)
         System.out.print(circuitMatrix[j][i] + " ");
         System.out.print("  " + circuitRightSide[j] + "\n");
         }
         System.out.print("\n");*/
        // make the new, simplified matrix
        int newsize = nn;
        double newmatx[][] = new double[newsize][newsize];
        double newrs[] = new double[newsize];
        int ii = 0;
        for (i = 0; i != matrixSize; i++) {
            RowInfo rri = circuitRowInfo[i];
            if (rri.dropRow) {
                rri.mapRow = -1;
                continue;
            }
            newrs[ii] = circuitRightSide[i];
            rri.mapRow = ii;
            //System.out.println("Row " + i + " maps to " + ii);
            for (j = 0; j != matrixSize; j++) {
                RowInfo ri = circuitRowInfo[j];
                if (ri.type == RowInfo.ROW_CONST) {
                    newrs[ii] -= ri.value * circuitMatrix[i][j];
                } else {
                    newmatx[ii][ri.mapCol] += circuitMatrix[i][j];
                }
            }
            ii++;
        }

        circuitMatrix = newmatx;
        circuitRightSide = newrs;
        matrixSize = circuitMatrixSize = newsize;
        for (i = 0; i != matrixSize; i++) {
            origRightSide[i] = circuitRightSide[i];
        }
        for (i = 0; i != matrixSize; i++) {
            for (j = 0; j != matrixSize; j++) {
                origMatrix[i][j] = circuitMatrix[i][j];
            }
        }
        circuitNeedsMap = true;

        /*
         System.out.println("matrixSize = " + matrixSize + " " + circuitNonLinear);
         for (j = 0; j != circuitMatrixSize; j++) {
         for (i = 0; i != circuitMatrixSize; i++)
         System.out.print(circuitMatrix[j][i] + " ");
         System.out.print("  " + circuitRightSide[j] + "\n");
         }
         System.out.print("\n");*/
        // if a matrix is linear, we can do the lu_factor here instead of
        // needing to do it every frame
        if (!circuitNonLinear) {
            if (!ElementUtils.lu_factor(circuitMatrix, circuitMatrixSize, circuitPermute)) {
                stop("Singular matrix!", null);

            }
        }
    }

    private void calcCircuitBottom() {
        int i;
        circuitBottom = 0;
        for (i = 0; i != elmList.size(); i++) {
            Rectangle rect = getElm(i).boundingBox;
            int bottom = rect.height + rect.y;
            if (bottom > circuitBottom) {
                circuitBottom = bottom;
            }
        }
    }

    public void stop(String s, CircuitElm ce) {
        stopMessage = s;
        circuitMatrix = null;
        stopElm = ce;
        stoppedCheck.setState(true);
        analyzeFlag = false;
        circuitCanvas.repaint();
    }

    // control voltage source vs with voltage from n1 to n2 (must
    // also call stampVoltageSource())
    public void stampVCVS(int n1, int n2, double coef, int vs) {
        int vn = nodeList.size() + vs;
        stampMatrix(vn, n1, coef);
        stampMatrix(vn, n2, -coef);
    }

    // stamp independent voltage source #vs, from n1 to n2, amount v
    public void stampVoltageSource(int n1, int n2, int vs, double v) {
        int vn = nodeList.size() + vs;
        stampMatrix(vn, n1, -1);
        stampMatrix(vn, n2, 1);
        stampRightSide(vn, v);
        stampMatrix(n1, vn, 1);
        stampMatrix(n2, vn, -1);
    }

    // use this if the amount of voltage is going to be updated in doStep()
    public void stampVoltageSource(int n1, int n2, int vs) {
        int vn = nodeList.size() + vs;
        stampMatrix(vn, n1, -1);
        stampMatrix(vn, n2, 1);
        stampRightSide(vn);
        stampMatrix(n1, vn, 1);
        stampMatrix(n2, vn, -1);
    }

    public void updateVoltageSource(int n1, int n2, int vs, double v) {
        int vn = nodeList.size() + vs;
        stampRightSide(vn, v);
    }

    public void stampResistor(int n1, int n2, double r) {
        double r0 = 1 / r;
        if (Double.isNaN(r0) || Double.isInfinite(r0)) {
            System.out.print("bad resistance " + r + " " + r0 + "\n");
            int a = 0;
            a /= a;
        }
        stampMatrix(n1, n1, r0);
        stampMatrix(n2, n2, r0);
        stampMatrix(n1, n2, -r0);
        stampMatrix(n2, n1, -r0);
    }

    public void stampConductance(int n1, int n2, double r0) {
        stampMatrix(n1, n1, r0);
        stampMatrix(n2, n2, r0);
        stampMatrix(n1, n2, -r0);
        stampMatrix(n2, n1, -r0);
    }

    // current from cn1 to cn2 is equal to voltage from vn1 to 2, divided by g
    public void stampVCCurrentSource(int cn1, int cn2, int vn1, int vn2, double g) {
        stampMatrix(cn1, vn1, g);
        stampMatrix(cn2, vn2, g);
        stampMatrix(cn1, vn2, -g);
        stampMatrix(cn2, vn1, -g);
    }

    public void stampCurrentSource(int n1, int n2, double i) {
        stampRightSide(n1, -i);
        stampRightSide(n2, i);
    }

    // stamp a current source from n1 to n2 depending on current through vs
    public void stampCCCS(int n1, int n2, int vs, double gain) {
        int vn = nodeList.size() + vs;
        stampMatrix(n1, vn, gain);
        stampMatrix(n2, vn, -gain);
    }

    // stamp value x in row i, column j, meaning that a voltage change
    // of dv in node j will increase the current into node i by x dv.
    // (Unless i or j is a voltage source node.)
    public void stampMatrix(int i, int j, double x) {
        if (i > 0 && j > 0) {
            if (circuitNeedsMap) {
                i = circuitRowInfo[i - 1].mapRow;
                RowInfo ri = circuitRowInfo[j - 1];
                if (ri.type == RowInfo.ROW_CONST) {
                    //System.out.println("Stamping constant " + i + " " + j + " " + x);
                    circuitRightSide[i] -= x * ri.value;
                    return;
                }
                j = ri.mapCol;
                //System.out.println("stamping " + i + " " + j + " " + x);
            } else {
                i--;
                j--;
            }
            circuitMatrix[i][j] += x;
        }
    }

    // stamp value x on the right side of row i, representing an
    // independent current source flowing into node i
    public void stampRightSide(int i, double x) {
        if (i > 0) {
            if (circuitNeedsMap) {
                i = circuitRowInfo[i - 1].mapRow;
                //System.out.println("stamping " + i + " " + x);
            } else {
                i--;
            }
            circuitRightSide[i] += x;
        }
    }

    // indicate that the value on the right side of row i changes in doStep()
    public void stampRightSide(int i) {
        //System.out.println("rschanges true " + (i-1));
        if (i > 0) {
            circuitRowInfo[i - 1].rsChanges = true;
        }
    }

    // indicate that the values on the left side of row i change in doStep()
    public void stampNonLinear(int i) {
        if (i > 0) {
            circuitRowInfo[i - 1].lsChanges = true;
        }
    }

    public double getIterCount() {
        if (speedBar.getValue() == 0) {
            return 0;
        }
        //return (Math.exp((speedBar.getValue()-1)/24.) + .5);
        return .1 * Math.exp((speedBar.getValue() - 61) / 24.);
    }

    private void runCircuit() {
        if (circuitMatrix == null || elmList.isEmpty()) {
            circuitMatrix = null;
            return;
        }
        int iter;
        //int maxIter = getIterCount();
        boolean debugprint = dumpMatrix;
        dumpMatrix = false;
        long tSteprate = (long) (160 * getIterCount());
        long tm = System.currentTimeMillis();
        long lit = lastIterTime;
        if (1000 >= tSteprate * (tm - lastIterTime)) {
            return;
        }
        for (iter = 1;; iter++) {
            int i, j, k, subiter;
            for (i = 0; i != elmList.size(); i++) {
                CircuitElm ce = getElm(i);
                ce.startIteration();
            }
            steps++;
            final int subiterCount = 5000;
            for (subiter = 0; subiter != subiterCount; subiter++) {
                converged = true;
                subIterations = subiter;
                for (i = 0; i != circuitMatrixSize; i++) {
                    circuitRightSide[i] = origRightSide[i];
                }
                if (circuitNonLinear) {
                    for (i = 0; i != circuitMatrixSize; i++) {
                        for (j = 0; j != circuitMatrixSize; j++) {
                            circuitMatrix[i][j] = origMatrix[i][j];
                        }
                    }
                }
                for (i = 0; i != elmList.size(); i++) {
                    CircuitElm ce = getElm(i);
                    ce.doStep();
                }
                if (stopMessage != null) {
                    return;
                }
                boolean printit = debugprint;
                debugprint = false;
                for (j = 0; j != circuitMatrixSize; j++) {
                    for (i = 0; i != circuitMatrixSize; i++) {
                        double x = circuitMatrix[i][j];
                        if (Double.isNaN(x) || Double.isInfinite(x)) {
                            stop("nan/infinite matrix!", null);
                            return;
                        }
                    }
                }
                if (printit) {
                    for (j = 0; j != circuitMatrixSize; j++) {
                        for (i = 0; i != circuitMatrixSize; i++) {
                            System.out.print(circuitMatrix[j][i] + ",");
                        }
                        System.out.print("  " + circuitRightSide[j] + "\n");
                    }
                    System.out.print("\n");
                }
                if (circuitNonLinear) {
                    if (converged && subiter > 0) {
                        break;
                    }
                    if (!ElementUtils.lu_factor(circuitMatrix, circuitMatrixSize,
                            circuitPermute)) {
                        stop("Singular matrix!", null);
                        return;
                    }
                }
                ElementUtils.lu_solve(circuitMatrix, circuitMatrixSize, circuitPermute,
                        circuitRightSide);

                for (j = 0; j != circuitMatrixFullSize; j++) {
                    RowInfo ri = circuitRowInfo[j];
                    double res;
                    if (ri.type == RowInfo.ROW_CONST) {
                        res = ri.value;
                    } else {
                        res = circuitRightSide[ri.mapCol];
                    }
                    /*System.out.println(j + " " + res + " " +
                     ri.type + " " + ri.mapCol);*/
                    if (Double.isNaN(res)) {
                        converged = false;
                        //debugprint = true;
                        break;
                    }
                    if (j < nodeList.size() - 1) {
                        CircuitNode cn = getCircuitNode(j + 1);
                        for (k = 0; k != cn.links.size(); k++) {
                            CircuitNodeLink cnl = (CircuitNodeLink) cn.links.get(k);
                            cnl.elm.setNodeVoltage(cnl.num, res);
                        }
                    } else {
                        int ji = j - (nodeList.size() - 1);
                        //System.out.println("setting vsrc " + ji + " to " + res);
                        voltageSources[ji].setCurrent(ji, res);
                    }
                }
                if (!circuitNonLinear) {
                    break;
                }
            }
            if (subiter > 5) {
                System.out.print("converged after " + subiter + " iterations\n");
            }
            if (subiter == subiterCount) {
                stop("Convergence failed!", null);
                break;
            }
            t += timeStep;
            tm = System.currentTimeMillis();
            lit = tm;
            if (iter * 1000 >= tSteprate * (tm - lastIterTime)
                    || (tm - lastFrameTime > 500)) {
                break;
            }
        }
        lastIterTime = lit;
        //System.out.println((System.currentTimeMillis()-lastFrameTime)/(double) iter);
    }

    public int min(int a, int b) {
        return (a < b) ? a : b;
    }

    public int max(int a, int b) {
        return (a > b) ? a : b;
    }

    public void editFuncPoint(int x, int y) {
        // XXX
        circuitCanvas.repaint(pause);
    }

    @Override
    public void componentHidden(ComponentEvent e) {
    }

    @Override
    public void componentMoved(ComponentEvent e) {
    }

    @Override
    public void componentShown(ComponentEvent e) {
        circuitCanvas.repaint();
    }

    @Override
    public void componentResized(ComponentEvent e) {
        handleResize();
        circuitCanvas.repaint(100);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        String ac = e.getActionCommand();
        if (e.getSource() == resetButton) {
            int i;

            // on IE, drawImage() stops working inexplicably every once in
            // a while.  Recreating it fixes the problem, so we do that here.
            dbimage = main.createImage(winSize.width, winSize.height);

            for (i = 0; i != elmList.size(); i++) {
                getElm(i).reset();
            }
            analyzeFlag = true;
            t = 0;
            stoppedCheck.setState(false);
            circuitCanvas.repaint();
        }
        if (e.getSource() == dumpMatrixButton) {
            dumpMatrix = true;
        }
        if (e.getSource() == optionsItem) {
            doEdit(new EditOptions(this));
        }
        if (e.getSource() == selectAllItem) {
            doSelectAll();
        }
        if (e.getSource() == exitItem) {
            destroyFrame();
            return;
        }
        if (e.getSource() == elmEditMenuItem) {
            doEdit(menuElm);
        }
        if (ac.compareTo("Delete") == 0) {
            if (e.getSource() != elmDeleteMenuItem) {
                menuElm = null;
            }
            doDelete();
        }
        if (ac.indexOf("setup ") == 0) {
            readSetupFile(ac.substring(6),
                    ((MenuItem) e.getSource()).getLabel());
        }
    }

    private void doEdit(Editable eable) {
        clearSelection();
        if (editDialog != null) {
            requestFocus();
            editDialog.setVisible(false);
            editDialog = null;
        }
        editDialog = new EditDialog(eable, this);
        editDialog.show();
    }


    public String dumpCircuit() {
        int i;
        int f = 0;
        f |= (smallGridCheckItem.getState()) ? 2 : 0;
        f |= (voltsCheckItem.getState()) ? 0 : 4;
        f |= (powerCheckItem.getState()) ? 8 : 0;
        f |= (showValuesCheckItem.getState()) ? 0 : 16;
        // 32 = linear scale in afilter
        String dump = "$ " + f + " "
                + timeStep + " " + getIterCount() + " "
                + currentBar.getValue() + " " + CircuitElm.voltageRange + " "
                + powerBar.getValue() + "\n";
        for (i = 0; i != elmList.size(); i++) {
            dump += getElm(i).dump() + "\n";
        }
        if (hintType != -1) {
            dump += "h " + hintType + " " + hintItem1 + " "
                    + hintItem2 + "\n";
        }
        return dump;
    }

    @Override
    public void adjustmentValueChanged(AdjustmentEvent e) {
        System.out.print(((Scrollbar) e.getSource()).getValue() + "\n");
    }

    private ByteArrayOutputStream readUrlData(URL url) throws java.io.IOException {
        Object o = url.getContent();
        FilterInputStream fis = (FilterInputStream) o;
        ByteArrayOutputStream ba = new ByteArrayOutputStream(fis.available());
        int blen = 1024;
        byte b[] = new byte[blen];
        while (true) {
            int len = fis.read(b);
            if (len <= 0) {
                break;
            }
            ba.write(b, 0, len);
        }
        return ba;
    }

    private URL getCodeBase() {
        try {
            if (applet != null) {
                return applet.getCodeBase();
            }
            File f = new File(".");
            return new URL("file:" + f.getCanonicalPath() + "/");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private void getSetupList(Menu menu, boolean retry) {
        Menu stack[] = new Menu[6];
        int stackptr = 0;
        stack[stackptr++] = menu;
        try {
            // hausen: if setuplist.txt does not exist in the same
            // directory, try reading from the jar file
            ByteArrayOutputStream ba;
            try {
                URL url = new URL(getCodeBase() + "setuplist.txt");
                ba = readUrlData(url);
            } catch (Exception e) {
                URL url = getClass().getClassLoader().getResource("setuplist.txt");
                ba = readUrlData(url);
            }
            // /hausen

            byte b[] = ba.toByteArray();
            int len = ba.size();
            int p;
            if (len == 0 || b[0] != '#') {
                // got a redirect, try again
                getSetupList(menu, true);
                return;
            }
            for (p = 0; p < len;) {
                int l;
                for (l = 0; l != len - p; l++) {
                    if (b[l + p] == '\n') {
                        l++;
                        break;
                    }
                }
                String line = new String(b, p, l - 1);
                if (line.charAt(0) != '#') {
                    if (line.charAt(0) == '+') {
                        Menu n = new Menu(line.substring(1));
                        menu.add(n);
                        menu = stack[stackptr++] = n;
                    } else if (line.charAt(0) == '-') {
                        menu = stack[--stackptr - 1];
                    } else {
                        int i = line.indexOf(' ');
                        if (i > 0) {
                            String title = line.substring(i + 1);
                            boolean first = false;
                            if (line.charAt(0) == '>') {
                                first = true;
                            }
                            String file = line.substring(first ? 1 : 0, i);
                            menu.add(getMenuItem(title, "setup " + file));
                            if (first && startCircuit == null) {
                                startCircuit = file;
                                startLabel = title;
                            }
                        }
                    }
                }
                p += l;
            }
        } catch (Exception e) {
            e.printStackTrace();
            stop("Can't read setuplist.txt!", null);
        }
    }

    public void readSetup(String text) {
        readSetup(text, false);
    }

    public void readSetup(String text, boolean retain) {
        readSetup(text.getBytes(), text.length(), retain);
        titleLabel.setText("untitled");
    }

    public void readSetupFile(String str, String title) {
        t = 0;
        System.out.println(str);
        try {
            URL url = new URL(getCodeBase() + "circuits/" + str);
            ByteArrayOutputStream ba = readUrlData(url);
            readSetup(ba.toByteArray(), ba.size(), false);
        } catch (Exception e1) {
            try {
                URL url = getClass().getClassLoader().getResource("circuits/" + str);
                ByteArrayOutputStream ba = readUrlData(url);
                readSetup(ba.toByteArray(), ba.size(), false);
            } catch (Exception e) {
                e.printStackTrace();
                stop("Unable to read " + str + "!", null);
            }
        }
        titleLabel.setText(title);
    }

    public void readSetup(byte b[], int len, boolean retain) {
        int i;
        if (!retain) {
            for (i = 0; i != elmList.size(); i++) {
                CircuitElm ce = getElm(i);
                ce.delete();
            }
            elmList.clear();
            hintType = -1;
            timeStep = 5e-6;
            smallGridCheckItem.setState(false);
            powerCheckItem.setState(false);
            voltsCheckItem.setState(true);
            showValuesCheckItem.setState(true);
            setGrid();
            speedBar.setValue(117); // 57
            currentBar.setValue(50);
            powerBar.setValue(50);
            CircuitElm.voltageRange = 5;
        }
        circuitCanvas.repaint();
        String[] lines = ElementUtils.parseString(new String(b));
        for (String line : lines) {
            StringTokenizer st = new StringTokenizer(line);
            while (st.hasMoreTokens()) {
                String type = st.nextToken();
                int tint = type.charAt(0);
                try {
                    if (tint == 'h') {
                        readHint(st);
                        break;
                    }
                    if (tint == '$') {
                        readOptions(st);
                        break;
                    }
                    if (tint == '%' || tint == '?' || tint == 'B') {
                        // ignore afilter-specific stuff
                        break;
                    }
                    if (tint >= '0' && tint <= '9') {
                        tint = Integer.parseInt(type);
                    }
                    int x1 = Integer.parseInt(st.nextToken());
                    int y1 = Integer.parseInt(st.nextToken());
                    int x2 = Integer.parseInt(st.nextToken());
                    int y2 = Integer.parseInt(st.nextToken());
                    int f = (int)Double.parseDouble(st.nextToken());
                    Class cls = dumpTypes[tint];
                    if (cls == null) {
                        System.out.println("unrecognized dump type: " + type);
                        break;
                    }
                    // find element class
                    Class carr[] = new Class[6];
                    //carr[0] = getClass();
                    carr[0] = carr[1] = carr[2] = carr[3] = carr[4]
                            = int.class;
                    carr[5] = StringTokenizer.class;
                    Constructor cstr;
                    cstr = cls.getConstructor(carr);

                    // invoke constructor with starting coordinates
                    Object oarr[] = new Object[6];
                    //oarr[0] = this;
                    oarr[0] = x1;
                    oarr[1] = y1;
                    oarr[2] = x2;
                    oarr[3] = y2;
                    oarr[4] = f;
                    oarr[5] = st;
                    CircuitElm ce = (CircuitElm) cstr.newInstance(oarr);
                    ce.setPoints();
                    elmList.add(ce);
                } catch (java.lang.reflect.InvocationTargetException ee) {
                    ee.getTargetException().printStackTrace();
                    break;
                } catch (Exception ee) {
                    ee.printStackTrace();
                    break;
                }
                break;
            }
        }
        enableItems();
        if (!retain) {
            handleResize(); // for scopes
        }
        needAnalyze();
    }

    private void readHint(StringTokenizer st) {
        hintType = Integer.parseInt(st.nextToken());
        hintItem1 = Integer.parseInt(st.nextToken());
        hintItem2 = Integer.parseInt(st.nextToken());
    }

    private void readOptions(StringTokenizer st) {
        int flags = Integer.parseInt(st.nextToken());
        smallGridCheckItem.setState((flags & 2) != 0);
        voltsCheckItem.setState((flags & 4) == 0);
        powerCheckItem.setState((flags & 8) == 8);
        showValuesCheckItem.setState((flags & 16) == 0);
        timeStep = Double.parseDouble(st.nextToken());
        double sp = Double.parseDouble(st.nextToken());
        int sp2 = (int) (Math.log(10 * sp) * 24 + 61.5);
        //int sp2 = (int) (Math.log(sp)*24+1.5);
        speedBar.setValue(sp2);
        currentBar.setValue(Integer.parseInt(st.nextToken()));
        CircuitElm.voltageRange = Double.parseDouble(st.nextToken());
        try {
            powerBar.setValue(Integer.parseInt(st.nextToken()));
        } catch (Exception e) {
        }
        setGrid();
    }

    public int snapGrid(int x) {
        return (x + gridRound) & gridMask;
    }

    private boolean doSwitch(int x, int y) {
        if (mouseElm == null || !(mouseElm instanceof SwitchElm)) {
            return false;
        }
        SwitchElm se = (SwitchElm) mouseElm;
        se.toggle();
        if (se.momentary) {
            heldSwitchElm = se;
        }
        needAnalyze();
        return true;
    }

    public int locateElm(CircuitElm elm) {
        int i;
        for (i = 0; i != elmList.size(); i++) {
            if (elm == elmList.get(i)) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        // ignore right mouse button with no modifiers (needed on PC)
        if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
            int ex = e.getModifiersEx();
            if ((ex & (MouseEvent.META_DOWN_MASK
                    | MouseEvent.SHIFT_DOWN_MASK
                    | MouseEvent.CTRL_DOWN_MASK
                    | MouseEvent.ALT_DOWN_MASK)) == 0) {
                return;
            }
        }
        if (!circuitArea.contains(e.getX(), e.getY())) {
            return;
        }
        if (dragElm != null) {
            dragElm.drag(e.getX(), e.getY());
        }
        boolean success = true;
        switch (tempMouseMode) {
            case MODE_DRAG_ALL:
                dragAll(snapGrid(e.getX()), snapGrid(e.getY()));
                break;
            case MODE_DRAG_ROW:
                dragRow(snapGrid(e.getX()), snapGrid(e.getY()));
                break;
            case MODE_DRAG_COLUMN:
                dragColumn(snapGrid(e.getX()), snapGrid(e.getY()));
                break;
            case MODE_DRAG_POST:
                if (mouseElm != null) {
                    dragPost(snapGrid(e.getX()), snapGrid(e.getY()));
                }
                break;
            case MODE_SELECT:
                if (mouseElm == null) {
                    selectArea(e.getX(), e.getY());
                } else {
                    tempMouseMode = MODE_DRAG_SELECTED;
                    success = dragSelected(e.getX(), e.getY());
                }
                break;
            case MODE_DRAG_SELECTED:
                success = dragSelected(e.getX(), e.getY());
                break;
        }
        dragging = true;
        if (success) {
            dragX = snapGrid(e.getX());
            dragY = snapGrid(e.getY());
        }
        circuitCanvas.repaint(pause);
    }

    private void dragAll(int x, int y) {
        int dx = x - dragX;
        int dy = y - dragY;
        if (dx == 0 && dy == 0) {
            return;
        }
        int i;
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            ce.move(dx, dy);
        }
        removeZeroLengthElements();
    }

    private void dragRow(int x, int y) {
        int dy = y - dragY;
        if (dy == 0) {
            return;
        }
        int i;
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            if (ce.y == dragY) {
                ce.movePoint(0, 0, dy);
            }
            if (ce.y2 == dragY) {
                ce.movePoint(1, 0, dy);
            }
        }
        removeZeroLengthElements();
    }

    private void dragColumn(int x, int y) {
        int dx = x - dragX;
        if (dx == 0) {
            return;
        }
        int i;
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            if (ce.x == dragX) {
                ce.movePoint(0, dx, 0);
            }
            if (ce.x2 == dragX) {
                ce.movePoint(1, dx, 0);
            }
        }
        removeZeroLengthElements();
    }

    private boolean dragSelected(int x, int y) {
        boolean me = false;
        if (mouseElm != null && !mouseElm.isSelected()) {
            mouseElm.setSelected(me = true);
        }

        // snap grid, unless we're only dragging text elements
        int i;
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            if (ce.isSelected()) {
                break;
            }
        }
        if (i != elmList.size()) {
            x = snapGrid(x);
            y = snapGrid(y);
        }

        int dx = x - dragX;
        int dy = y - dragY;
        if (dx == 0 && dy == 0) {
            // don't leave mouseElm selected if we selected it above
            if (me) {
                mouseElm.setSelected(false);
            }
            return false;
        }
        boolean allowed = true;

        // check if moves are allowed
        for (i = 0; allowed && i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            if (ce.isSelected() && !ce.allowMove(dx, dy)) {
                allowed = false;
            }
        }

        if (allowed) {
            for (i = 0; i != elmList.size(); i++) {
                CircuitElm ce = getElm(i);
                if (ce.isSelected()) {
                    ce.move(dx, dy);
                }
            }
            needAnalyze();
        }

        // don't leave mouseElm selected if we selected it above
        if (me) {
            mouseElm.setSelected(false);
        }

        return allowed;
    }

    private void dragPost(int x, int y) {
        if (draggingPost == -1) {
            draggingPost
                    = (distanceSq(mouseElm.x, mouseElm.y, x, y)
                    > distanceSq(mouseElm.x2, mouseElm.y2, x, y)) ? 1 : 0;
        }
        int dx = x - dragX;
        int dy = y - dragY;
        if (dx == 0 && dy == 0) {
            return;
        }
        mouseElm.movePoint(draggingPost, dx, dy);
        needAnalyze();
    }

    private void selectArea(int x, int y) {
        int x1 = min(x, initDragX);
        int x2 = max(x, initDragX);
        int y1 = min(y, initDragY);
        int y2 = max(y, initDragY);
        selectedArea = new Rectangle(x1, y1, x2 - x1, y2 - y1);
        int i;
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            ce.selectRect(selectedArea);
        }
    }

    private void setSelectedElm(CircuitElm cs) {
        int i;
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            ce.setSelected(ce == cs);
        }
        mouseElm = cs;
    }

    private void removeZeroLengthElements() {
        int i;
        boolean changed = false;
        for (i = elmList.size() - 1; i >= 0; i--) {
            CircuitElm ce = getElm(i);
            if (ce.x == ce.x2 && ce.y == ce.y2) {
                elmList.remove(i);
                ce.delete();
                changed = true;
            }
        }
        needAnalyze();
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
            return;
        }
        int x = e.getX();
        int y = e.getY();
        dragX = snapGrid(x);
        dragY = snapGrid(y);
        draggingPost = -1;
        int i;
        CircuitElm origMouse = mouseElm;
        mouseElm = null;
        mousePost = -1;
        plotXElm = plotYElm = null;
        int bestDist = 100000;
        int bestArea = 100000;
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            if (ce.boundingBox.contains(x, y)) {
                int j;
                int area = ce.boundingBox.width * ce.boundingBox.height;
                int jn = ce.getPostCount();
                if (jn > 2) {
                    jn = 2;
                }
                for (j = 0; j != jn; j++) {
                    Point pt = ce.getPost(j);
                    int dist = distanceSq(x, y, pt.x, pt.y);

                    // if multiple elements have overlapping bounding boxes,
                    // we prefer selecting elements that have posts close
                    // to the mouse pointer and that have a small bounding
                    // box area.
                    if (dist <= bestDist && area <= bestArea) {
                        bestDist = dist;
                        bestArea = area;
                        mouseElm = ce;
                    }
                }
                if (ce.getPostCount() == 0) {
                    mouseElm = ce;
                }
            }
        }
        if (mouseElm == null) {
            // the mouse pointer was not in any of the bounding boxes, but we
            // might still be close to a post
            for (i = 0; i != elmList.size(); i++) {
                CircuitElm ce = getElm(i);
                int j;
                int jn = ce.getPostCount();
                for (j = 0; j != jn; j++) {
                    Point pt = ce.getPost(j);
                    if (distanceSq(pt.x, pt.y, x, y) < 26) {
                        mouseElm = ce;
                        mousePost = j;
                        break;
                    }
                }
            }
        } else {
            mousePost = -1;
            // look for post close to the mouse pointer
            for (i = 0; i != mouseElm.getPostCount(); i++) {
                Point pt = mouseElm.getPost(i);
                if (distanceSq(pt.x, pt.y, x, y) < 26) {
                    mousePost = i;
                }
            }
        }
        if (mouseElm != origMouse) {
            circuitCanvas.repaint();
        }
    }

    private int distanceSq(int x1, int y1, int x2, int y2) {
        x2 -= x1;
        y2 -= y1;
        return x2 * x2 + y2 * y2;
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        if (e.getClickCount() == 2 && !didSwitch) {
            doEditMenu(e);
        }
        if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
            if (mouseMode == MODE_SELECT || mouseMode == MODE_DRAG_SELECTED) {
                clearSelection();
            }
        }
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
        mouseElm = plotXElm = plotYElm = null;
        circuitCanvas.repaint();
    }

    @Override
    public void mousePressed(MouseEvent e) {
        didSwitch = false;

        System.out.println(e.getModifiers());
        int ex = e.getModifiersEx();
        if ((ex & (MouseEvent.META_DOWN_MASK
                | MouseEvent.SHIFT_DOWN_MASK)) == 0 && e.isPopupTrigger()) {
            doPopupMenu(e);
            return;
        }
        if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
            // left mouse
            tempMouseMode = mouseMode;
            if ((ex & MouseEvent.ALT_DOWN_MASK) != 0
                    && (ex & MouseEvent.META_DOWN_MASK) != 0) {
                tempMouseMode = MODE_DRAG_COLUMN;
            } else if ((ex & MouseEvent.ALT_DOWN_MASK) != 0
                    && (ex & MouseEvent.SHIFT_DOWN_MASK) != 0) {
                tempMouseMode = MODE_DRAG_ROW;
            } else if ((ex & MouseEvent.SHIFT_DOWN_MASK) != 0) {
                tempMouseMode = MODE_SELECT;
            } else if ((ex & MouseEvent.ALT_DOWN_MASK) != 0) {
                tempMouseMode = MODE_DRAG_ALL;
            } else if ((ex & (MouseEvent.CTRL_DOWN_MASK
                    | MouseEvent.META_DOWN_MASK)) != 0) {
                tempMouseMode = MODE_DRAG_POST;
            }
        } else if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
            // right mouse
            if ((ex & MouseEvent.SHIFT_DOWN_MASK) != 0) {
                tempMouseMode = MODE_DRAG_ROW;
            } else if ((ex & (MouseEvent.CTRL_DOWN_MASK
                    | MouseEvent.META_DOWN_MASK)) != 0) {
                tempMouseMode = MODE_DRAG_COLUMN;
            } else {
                return;
            }
        }

        if (tempMouseMode != MODE_SELECT && tempMouseMode != MODE_DRAG_SELECTED) {
            clearSelection();
        }
        if (doSwitch(e.getX(), e.getY())) {
            didSwitch = true;
            return;
        }

        initDragX = e.getX();
        initDragY = e.getY();
        dragging = true;
        if (tempMouseMode != MODE_ADD_ELM || addingClass == null) {
            return;
        }

        int x0 = snapGrid(e.getX());
        int y0 = snapGrid(e.getY());
        if (!circuitArea.contains(x0, y0)) {
            return;
        }

        dragElm = ElementUtils.constructElement(addingClass, x0, y0);
    }

    // hausen: add doEditMenu
    private void doEditMenu(MouseEvent e) {
        if (mouseElm != null) {
            doEdit(mouseElm);
        }
    }

    private void doPopupMenu(MouseEvent e) {
        menuElm = mouseElm;
        if (mouseElm != null) {
            elmEditMenuItem.setEnabled(mouseElm.getEditInfo(0) != null);
            elmMenu.show(e.getComponent(), e.getX(), e.getY());
        } else {
            doMainMenuChecks(mainMenu);
            mainMenu.show(e.getComponent(), e.getX(), e.getY());
        }
    }

    private void doMainMenuChecks(Menu m) {
        int i;
        if (m == optionsMenu) {
            return;
        }
        for (i = 0; i != m.getItemCount(); i++) {
            MenuItem mc = m.getItem(i);
            if (mc instanceof Menu) {
                doMainMenuChecks((Menu) mc);
            }
            if (mc instanceof CheckboxMenuItem) {
                CheckboxMenuItem cmi = (CheckboxMenuItem) mc;
                cmi.setState(
                        mouseModeStr.compareTo(cmi.getActionCommand()) == 0);
            }
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        int ex = e.getModifiersEx();
        if ((ex & (MouseEvent.SHIFT_DOWN_MASK | MouseEvent.CTRL_DOWN_MASK
                | MouseEvent.META_DOWN_MASK)) == 0 && e.isPopupTrigger()) {
            doPopupMenu(e);
            return;
        }
        tempMouseMode = mouseMode;
        selectedArea = null;
        dragging = false;
        boolean circuitChanged = false;
        if (heldSwitchElm != null) {
            heldSwitchElm.mouseUp();
            heldSwitchElm = null;
            circuitChanged = true;
        }
        if (dragElm != null) {
            // if the element is zero size then don't create it
            if (dragElm.x == dragElm.x2 && dragElm.y == dragElm.y2) {
                dragElm.delete();
            } else {
                elmList.add(dragElm);
                circuitChanged = true;
            }
            dragElm = null;
        }
        if (circuitChanged) {
            needAnalyze();
        }
        if (dragElm != null) {
            dragElm.delete();
        }
        dragElm = null;
        circuitCanvas.repaint();
    }

    private void enableItems() {
        if (powerCheckItem.getState()) {
            powerBar.enable();
            powerLabel.enable();
        } else {
            powerBar.disable();
            powerLabel.disable();
        }
    }

    @Override
    public void itemStateChanged(ItemEvent e) {
        circuitCanvas.repaint(pause);
        Object mi = e.getItemSelectable();
        if (mi == stoppedCheck) {
            return;
        }
        if (mi == smallGridCheckItem) {
            setGrid();
        }
        if (mi == powerCheckItem) {
            if (powerCheckItem.getState()) {
                voltsCheckItem.setState(false);
            } else {
                voltsCheckItem.setState(true);
            }
        }
        if (mi == voltsCheckItem && voltsCheckItem.getState()) {
            powerCheckItem.setState(false);
        }
        enableItems();
        if (mi instanceof CheckboxMenuItem) {
            MenuItem mmi = (MenuItem) mi;
            int prevMouseMode = mouseMode;
            setMouseMode(MODE_ADD_ELM);
            String s = mmi.getActionCommand();
            if (s.length() > 0) {
                mouseModeStr = s;
            }
            if (s.compareTo("DragAll") == 0) {
                setMouseMode(MODE_DRAG_ALL);
            } else if (s.compareTo("DragRow") == 0) {
                setMouseMode(MODE_DRAG_ROW);
            } else if (s.compareTo("DragColumn") == 0) {
                setMouseMode(MODE_DRAG_COLUMN);
            } else if (s.compareTo("DragSelected") == 0) {
                setMouseMode(MODE_DRAG_SELECTED);
            } else if (s.compareTo("DragPost") == 0) {
                setMouseMode(MODE_DRAG_POST);
            } else if (s.compareTo("Select") == 0) {
                setMouseMode(MODE_SELECT);
            } else if (s.length() > 0) {
                try {
                    addingClass = Class.forName("com.circuit.elements." + s);
                } catch (Exception ee) {
                    ee.printStackTrace();
                }
            } else {
                setMouseMode(prevMouseMode);
            }
            tempMouseMode = mouseMode;
        }
    }

    private void setGrid() {
        gridSize = (smallGridCheckItem.getState()) ? 8 : 16;
        gridMask = ~(gridSize - 1);
        gridRound = gridSize / 2 - 1;
    }

    private void setMouseMode(int mode) {
        mouseMode = mode;
        if (mode == MODE_ADD_ELM) {
            circuitCanvas.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
        } else {
            circuitCanvas.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
        }
    }

    private void setMenuSelection() {
        if (menuElm != null) {
            if (menuElm.selected) {
                return;
            }
            clearSelection();
            menuElm.setSelected(true);
        }
    }

    private void doDelete() {
        int i;
        setMenuSelection();
        boolean hasDeleted = false;

        for (i = elmList.size() - 1; i >= 0; i--) {
            CircuitElm ce = getElm(i);
            if (ce.isSelected()) {
                ce.delete();
                elmList.remove(i);
                hasDeleted = true;
            }
        }

        if (!hasDeleted) {
            for (i = elmList.size() - 1; i >= 0; i--) {
                CircuitElm ce = getElm(i);
                if (ce == mouseElm) {
                    ce.delete();
                    elmList.remove(i);
                    hasDeleted = true;
                    mouseElm = null;
                    break;
                }
            }
        }

        if (hasDeleted) {
            needAnalyze();
        }
    }

    private void clearSelection() {
        int i;
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            ce.setSelected(false);
        }
    }

    private void doSelectAll() {
        int i;
        for (i = 0; i != elmList.size(); i++) {
            CircuitElm ce = getElm(i);
            ce.setSelected(true);
        }
    }

    @Override
    public void keyPressed(KeyEvent e) {
    }

    @Override
    public void keyReleased(KeyEvent e) {
    }

    @Override
    public void keyTyped(KeyEvent e) {
        if (e.getKeyChar() == 127) {
            doDelete();
            return;
        }
        if (e.getKeyChar() > ' ' && e.getKeyChar() < 127) {
            Class c = shortcuts[e.getKeyChar()];
            if (c == null) {
                return;
            }
            CircuitElm elm = ElementUtils.constructElement(c, 0, 0);
            if (elm == null) {
                return;
            }
            setMouseMode(MODE_ADD_ELM);
            mouseModeStr = c.getName();
            addingClass = c;
        }
        if (e.getKeyChar() == ' ' || e.getKeyChar() == KeyEvent.VK_ESCAPE) {
            setMouseMode(MODE_SELECT);
            mouseModeStr = "Select";
        }
        tempMouseMode = mouseMode;
    }
}
